﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using ElectronicCommerce.Framework.Configuration;
using ElectronicCommerce.Framework.Data.Configuration;
using ElectronicCommerce.Framework.Data.Entity;
using ElectronicCommerce.Framework.Data.Utility;
using ElectronicCommerce.Framework.Logging;

namespace ElectronicCommerce.Framework.Data
{
    /// <summary>
    /// Lite data access 
    /// SQL server only
    /// </summary>
    public class DataCommand : ICloneable, IDisposable
    {
        #region [ Variables ]

        private int connectionStringIndex = 0;
        private int currentRetryCount = 0;

        private int currentConnectionReTryCount = 0;

        protected DataOperationCommand operationCommand = null;
        private SqlTransaction transaction = null;
        private SqlConnection connection = null;
        private SqlParameter[] parameters = null;
        private bool supportTransaction = false;
        private bool isParametersDirty = false;

        private const string LOG_CATEGORY_NAME = "ElectronicCommerce.Framework.Data.DataCommand";

        #endregion

        #region [ Properites ]

        internal DataOperationCommand OperationCommand
        {
            get { return operationCommand; }
        }

        internal String[] ConnectionStrings
        {
            get;
            set;
        }

        public string CommandText
        {
            get
            {
                return this.OperationCommand.CommandText;
            }
            set
            {
                this.OperationCommand.CommandText = value;
            }
        }

        #endregion

        #region [ Constructors ]

        internal DataCommand(DataOperationCommand command)
        {
            operationCommand = command;

            if (!string.IsNullOrEmpty(command.ConnectionStringName))
            {
                ConnectionStrings = GetConnectionStringByName(command.ConnectionStringName, command.DatabaseType);
            }

            supportTransaction = false;

            if (command.Parameters != null && command.Parameters.ParameterList != null
                && command.Parameters.ParameterList.Length > 0)
            {
                parameters = new SqlParameter[command.Parameters.ParameterList.Length];
                for (int index = 0; index < command.Parameters.ParameterList.Length; index++)
                {
                    DataOperationParameter param = command.Parameters.ParameterList[index];
                    parameters[index] = (param.GetDbParameter());
                }
            }

        }

        private DataCommand()
        {
        }

        public object Clone()
        {
            DataCommand cmd = new DataCommand();
            cmd.connection = this.connection;
            if (this.ConnectionStrings != null && this.ConnectionStrings.Count() != 0)
            {
                cmd.ConnectionStrings = (string[])this.ConnectionStrings.Clone();
            }
            cmd.transaction = this.transaction;
            cmd.supportTransaction = this.supportTransaction;
            if (this.parameters != null && this.parameters.Length > 0)
            {
                SqlParameter[] parms = new SqlParameter[this.parameters.Length];
                for (int index = 0; index < this.parameters.Length; index++)
                {
                    SqlParameter param = new SqlParameter();
                    param.ParameterName = this.parameters[index].ParameterName;
                    param.DbType = this.parameters[index].DbType;
                    param.Direction = this.parameters[index].Direction;

                    if (this.parameters[index].Size != -1)
                    {
                        param.Size = this.parameters[index].Size;
                    }

                    parms[index] = param;
                }
                cmd.parameters = parms;
            }

            cmd.operationCommand = CloneCommand(this.operationCommand);

            return cmd;
        }

        /// <summary>
        /// Clones the command.
        /// </summary>
        /// <param name="command">The CMD.</param>
        /// <returns></returns>
        private static DataOperationCommand CloneCommand(DataOperationCommand cmd)
        {
            if (cmd == null)
            {
                return null;
            }

            if (cmd is ICloneable)
            {
                return ((ICloneable)cmd).Clone() as DataOperationCommand;
            }
            else
            {
                throw new ApplicationException("A class that implements IClonable is expected.");
            }
        }

        /// <summary>
        /// Copies the command from command while keeping the transaction context.
        /// </summary>
        /// <param name="command">The command.</param>
        internal void CopyCommand(DataCommand command)
        {
            operationCommand = CloneCommand(command.operationCommand);
        }

        /// <summary>
        /// Gets or sets a value indicating whether this command is executed in a support transaction.
        /// If the command is executed in a transaction and the transaction is not spcifically committed using CommitTransaction(),
        /// the underlying transaction will be rolled back.
        /// </summary>
        /// <value><c>true</c> if [support transaction]; otherwise, <c>false</c>.</value>
        public bool SupportTransaction
        {
            get { return supportTransaction; }
            internal set
            {
                supportTransaction = value;
            }
        }

        #endregion

        #region [ Parameters ]

        /// <summary>
        /// get a parameter value
        /// </summary>
        /// <param name="paramName"></param>
        /// <returns></returns>
        public object GetParameterValue(string paramName)
        {
            foreach (SqlParameter param in parameters)
            {
                if (param.ParameterName == paramName)
                {
                    return param.Value;
                }
            }
            return null;
        }

        public DataCommand SetParameterValue(string paramName, object val)
        {
            foreach (SqlParameter param in parameters)
            {
                if (param.ParameterName == paramName)
                {
                    param.Value = val;
                }
            }

            return this;
        }

        public void SetParameters(SqlParameter[] parameters)
        {
            this.parameters = parameters;
        }

        #endregion

        #region [ Connection && Transaction ]

        private void PrepareConnectionAndTransaction()
        {
            connection = new SqlConnection();
            connection.ConnectionString = this.ConnectionStrings[connectionStringIndex];

            if (supportTransaction && transaction == null)
            {
                try
                {
                    if (connection.State != ConnectionState.Open)
                    {
                        connection.Open();
                    }
                }
                catch
                {
                    connection = null;
                    return;
                }

                try
                {
                    transaction = connection.BeginTransaction();
                }
                catch
                {
                    connection.Close();
                    connection = null;
                    transaction = null;
                }
            }

            if (isParametersDirty && this.parameters != null && this.parameters.Length > 0)
            {
                SqlParameter[] parms = new SqlParameter[this.parameters.Length];
                for (int index = 0; index < this.parameters.Length; index++)
                {
                    SqlParameter param = new SqlParameter();
                    param.ParameterName = this.parameters[index].ParameterName;
                    param.DbType = this.parameters[index].DbType;
                    param.Direction = this.parameters[index].Direction;
                    param.Value = this.parameters[index].Value;

                    if (this.parameters[index].Size != -1)
                    {
                        param.Size = this.parameters[index].Size;
                    }

                    parms[index] = param;
                }
                this.parameters = parms;
            }
        }

        /// <summary>
        /// Commits the transaction.
        /// </summary>
        public void CommitTransaction()
        {
            try
            {
                if (transaction != null)
                {
                    if (connection.State != ConnectionState.Open)
                    {
                        connection.Open();
                    }
                    transaction.Commit();
                }
            }
            finally
            {
                if (connection != null && connection.State != ConnectionState.Closed)
                {
                    connection.Close();
                }

                connection = null;
                transaction = null;
            }
        }

        /// <summary>
        /// Rolls back the transaction.
        /// </summary>
        public void RollbackTransaction()
        {
            try
            {
                transaction.Rollback();
            }
            finally
            {
                if (supportTransaction && connection != null)
                {
                    connection.Close();
                }
                connection = null;
                transaction = null;
            }
        }

        #endregion

        #region [ Execution ]

        #region [ ExecuteScalar ]

        public T ExecuteScalar<T>()
        {
            Exception ex = null;
            while (ProcessExceptionAndReTry(ex))
            {
                try
                {
                    return TryExecuteScalar<T>();
                }
                catch (Exception iex)
                {
                    ex = iex;
                }
            }

            LogExecutionError(ex);
            throw ex;

        }

        private T TryExecuteScalar<T>()
        {
            try
            {
                PrepareConnectionAndTransaction();
                if (transaction == null)
                {
                    return (T)SqlHelper.ExecuteScalar(this.connection
                        , operationCommand.CommandType
                        , operationCommand.CommandText
                        , parameters);
                }
                else
                {
                    return (T)SqlHelper.ExecuteScalar(this.transaction, operationCommand.CommandType
                        , operationCommand.CommandText, parameters);
                }
            }
            catch
            {
                throw;
            }
            finally
            {
                if (connection != null && connection.State != ConnectionState.Closed)
                {
                    connection.Close();
                }

                connection = null;
                transaction = null;
            }
        }

        public object ExecuteScalar()
        {
            Exception ex = null;
            while (ProcessExceptionAndReTry(ex))
            {
                try
                {
                    return TryExecuteScalar();
                }
                catch (Exception iex)
                {
                    ex = iex;
                }
            }

            LogExecutionError(ex);
            throw ex;
        }

        private object TryExecuteScalar()
        {
            try
            {
                PrepareConnectionAndTransaction();
                if (transaction == null)
                {
                    return SqlHelper.ExecuteScalar(this.connection
                        , operationCommand.CommandType
                        , operationCommand.CommandText
                        , parameters);
                }
                else
                {
                    return SqlHelper.ExecuteScalar(this.transaction, operationCommand.CommandType
                        , operationCommand.CommandText, parameters);
                }
            }
            catch
            {
                throw;
            }
            finally
            {
                if (!SupportTransaction)
                {
                    if (connection != null && connection.State != ConnectionState.Closed)
                    {
                        connection.Close();
                    }

                    connection = null;
                    transaction = null;
                }
            }
        }

        #endregion

        #region [ ExecuteNonQuery ]

        public int ExecuteNonQuery()
        {
            Exception ex = null;
            while (ProcessExceptionAndReTry(ex))
            {
                try
                {
                    return TryExecuteNonQuery();
                }
                catch (Exception iex)
                {
                    ex = iex;
                }
            }

            LogExecutionError(ex);
            throw ex;

        }

        private int TryExecuteNonQuery()
        {
            try
            {
                PrepareConnectionAndTransaction();

                if (transaction == null)
                {
                    return SqlHelper.ExecuteNonQuery(this.connection
                        , operationCommand.CommandType
                        , operationCommand.CommandText
                        , parameters);
                }
                else
                {
                    return SqlHelper.ExecuteNonQuery(this.transaction, operationCommand.CommandType
                        , operationCommand.CommandText, parameters);
                }
            }
            catch
            {
                throw;
            }
            finally
            {
                if (!SupportTransaction)
                {
                    if (connection != null && connection.State != ConnectionState.Closed)
                    {
                        connection.Close();
                    }

                    connection = null;
                    transaction = null;
                }
            }
        }

        #endregion

        #region [ ExecuteDataReader ]

        public IDataReader ExecuteDataReader()
        {
            Exception ex = null;
            while (ProcessExceptionAndReTry(ex))
            {
                try
                {
                    return TryExecuteDataReader();
                }
                catch (Exception iex)
                {
                    ex = iex;
                }
            }

            LogExecutionError(ex);
            throw ex;

        }

        private IDataReader TryExecuteDataReader()
        {
            try
            {
                PrepareConnectionAndTransaction();

                if (transaction == null)
                {
                    IDataReader reader = SqlHelper.ExecuteReader(this.connection, operationCommand.CommandType
                            , operationCommand.CommandText, true, parameters);

                    return reader;
                }
                else
                {
                    IDataReader reader = SqlHelper.ExecuteReader(this.transaction, operationCommand.CommandType
                            , operationCommand.CommandText, true, parameters);

                    return reader;
                }
            }
            catch
            {

                if (connection != null && connection.State != ConnectionState.Closed)
                {
                    connection.Close();
                }

                connection = null;
                transaction = null;

                throw;

            }
        }

        #endregion

        #region [ ExecuteDataSet ]

        public DataSet ExecuteDataSet()
        {
            Exception ex = null;
            while (ProcessExceptionAndReTry(ex))
            {
                try
                {
                    return TryExecuteDataSet();
                }
                catch (Exception iex)
                {
                    ex = iex;
                }
            }

            throw ex;
        }

        private DataSet TryExecuteDataSet()
        {
            try
            {
                PrepareConnectionAndTransaction();

                return SqlHelper.ExecuteDataset(this.connection
                    , operationCommand.CommandType
                    , operationCommand.CommandText
                   , parameters);
            }
            catch
            {
                throw;
            }
            finally
            {
                if (connection != null && connection.State != ConnectionState.Closed)
                {
                    connection.Close();
                }

                connection = null;
                transaction = null;
            }
        }

        #endregion

        #region  [ ExecuteDataTable ]

        public DataTable ExecuteDataTable()
        {
            return ExecuteDataSet().Tables[0];
        }

        #endregion

        #region [ ExecuteEntity ]

        public T ExecuteEntity<T>() where T : class, new()
        {
            Exception ex = null;
            while (ProcessExceptionAndReTry(ex))
            {
                try
                {
                    return TryExecuteEntity<T>();
                }
                catch (Exception iex)
                {
                    ex = iex;
                }
            }

            LogExecutionError(ex);

            throw ex;

        }

        private T TryExecuteEntity<T>() where T : class, new()
        {
            IDataReader reader = null;
            try
            {
                reader = ExecuteDataReader();

                if (reader.Read())
                {
                    return EntityBuilder.BuildEntity<T>(reader);
                }
                else
                {
                    return null;
                }
            }
            catch
            {
                throw;

            }
            finally
            {
                if (reader != null)
                {
                    reader.Close();
                    reader.Dispose();
                }


                if (connection != null && connection.State != ConnectionState.Closed)
                {
                    connection.Close();
                }

                connection = null;
                transaction = null;

            }
        }

        #endregion

        #region [ ExecuteEntityList ]

        public List<T> ExecuteEntityList<T>() where T : class, new()
        {
            Exception ex = null;
            while (ProcessExceptionAndReTry(ex))
            {
                try
                {
                    return TryExecuteEntityList<T>();
                }
                catch (Exception iex)
                {
                    ex = iex;
                }
            }

            throw ex;

        }

        private List<T> TryExecuteEntityList<T>() where T : class, new()
        {
            List<T> list = new List<T>();
            IDataReader reader = null;
            try
            {
                reader = ExecuteDataReader();

                while (reader.Read())
                {
                    T entity = EntityBuilder.BuildEntity<T>(reader);
                    list.Add(entity);
                }

                return list;
            }
            catch
            {
                throw;

            }
            finally
            {
                if (reader != null)
                {
                    reader.Close();
                    reader.Dispose();
                }

                if (connection != null && connection.State != ConnectionState.Closed)
                {
                    connection.Close();
                }

                connection = null;
                transaction = null;
            }
        }

        #endregion

        #endregion

        #region [ IDispose ]
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool isDisposing)
        {
            // if called in Dispose, commit transaction.
            // otherwise, let the runtime perform GC.
            if (isDisposing)
            {
                CommitTransaction();
            }
        }
        #endregion

        #region [ Helpers ]

        private bool ProcessExceptionAndReTry(Exception ex)
        {
            if (ex == null)
            {
                isParametersDirty = false;
                currentRetryCount = 0;
                currentConnectionReTryCount = 0;
                currentRetryCount++;
            }
            else
            {
                isParametersDirty = true;
                if (currentConnectionReTryCount == 0)
                {
                    LogExecutionReTry();
                }

                currentConnectionReTryCount++;

                bool isNextConnection = false;

                //一个链接字符串可以调用3次，如果三次都调用失败，则调用下一个Connectionstring
                if (currentConnectionReTryCount >= GetRetryTimes(operationCommand.ConnectionStringName))
                {
                    currentRetryCount++;
                    isNextConnection = true;
                }

                if (currentRetryCount >= ConnectionStrings.Length)
                {
                    return false;
                }

                if (isNextConnection)
                {
                    connectionStringIndex++;
                    currentConnectionReTryCount = 0;

                    if (connectionStringIndex >= ConnectionStrings.Length)
                    {
                        connectionStringIndex = 0;
                    }
                }
            }

            return true;
        }

        private int GetRetryTimes(string connectionStringName)
        {
            try
            {
                return ConfigManager.GetConfig<ConnectionStringConfigs>().DatabaseList[operationCommand.ConnectionStringName].RetryTimes;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
            }

            return 0;
        }

        private string[] GetConnectionStringByName(string connectionStringName, DatabaseTypeEnum type)
        {
            try
            {
                return ConfigManager.GetConfig<ConnectionStringConfigs>().DatabaseList[connectionStringName].ConnectionStringList[type.ToString()].ConnectionStrings;
            }
            catch (Exception ex)
            {
                throw new Exception("Can't find connectionString key: [{0}] in connectionString config.", ex);
            }
        }

        private void LogExecutionError(Exception ex)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("DataCommand Execution error, command text:");
            sb.AppendLine(operationCommand.CommandText);
            if (parameters != null && parameters.Length > 0)
            {
                sb.Append(System.Environment.NewLine);
                sb.Append("Command Parameters:");
                foreach (DbParameter parameter in parameters)
                {
                    sb.Append(parameter.ParameterName);
                    sb.Append("=");
                    sb.Append(parameter.Value);
                    sb.Append(",");
                }
            }
            sb.Append(System.Environment.NewLine);
            sb.AppendLine("Exception: ");
            sb.AppendLine(ex.ToString());

            Logging.Logger.Log(LOG_CATEGORY_NAME, LogLevel.Error, sb.ToString());

            throw ex;
        }

        private void LogExecutionReTry()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Database execution retry log, command text:");
            sb.AppendLine(operationCommand.CommandText);
            if (parameters != null && parameters.Length > 0)
            {
                sb.Append(System.Environment.NewLine);
                sb.Append("Command Parameters:");
                for (int index = 0; index < parameters.Length; index++)
                {
                    DbParameter parameter = parameters[index];
                    sb.Append(parameter.ParameterName);
                    sb.Append("=");
                    sb.Append(parameter.Value);
                    if (index < parameters.Length)
                    {
                        sb.Append(",");
                    }
                }
            }
            sb.AppendLine(System.Environment.NewLine);
            sb.AppendLine("Connection String Names :");
            sb.AppendLine(System.Environment.NewLine);
            for (int i = 0; i < ConnectionStrings.Length; i++)
            {
                //junyu [2011-7-12]
                //remove passwords
                string connString = this.ConnectionStrings[i];
                int idx = connString.IndexOf("Password=", StringComparison.OrdinalIgnoreCase);
                sb.AppendLine(connString.Substring(0, idx == -1 ? 50 : idx));
                sb.AppendLine(System.Environment.NewLine);
            }

            Logging.Logger.Log(LOG_CATEGORY_NAME, LogLevel.Info, sb.ToString());

        }

        #endregion
    }
}
